﻿using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nvigorate.Relational.Mapping;

namespace Nvigorate.Test.Relational.Binding
{
    public class AssignmentTests
    {
        #region Fields

        private const string ELEMENT_A_MINIMUM =
            "<assignment target=\"A\" source=\"A\"/>";

        private const string ELEMENT_A_PERSISTABLE =
            "<assignment target=\"A\" source=\"A\" persistable=\"false\" />";

        private const string ELEMENT_A_PERSISTABLE_TRUE =
            "<assignment target=\"A\" source=\"A\" persistable=\"true\" />";

        private const string ELEMENT_A_SOURCE_KEY =
            "<assignment target=\"A\" source=\"A\" sourceKey=\"true\" />";

        private const string ELEMENT_A_SOURCE_KEY_FALSE =
            "<assignment target=\"A\" source=\"A\" sourceKey=\"false\" />";

        private const string ELEMENT_A_TARGET_KEY =
            "<assignment target=\"A\" source=\"A\" targetKey=\"true\" />";

        private const string ELEMENT_A_TARGET_KEY_FALSE =
            "<assignment target=\"A\" source=\"A\" targetKey=\"false\" />";

        private const string ELEMENT_A_NULLABLE =
            "<assignment target=\"A\" source=\"A\" nullable=\"false\" />";

        private const string ELEMENT_A_NULLABLE_TRUE =
            "<assignment target=\"A\" source=\"A\" nullable=\"true\" />";

        private const string ELEMENT_A_GENERATED =
            "<assignment source=\"A\" target=\"A\" persistable=\"true\" nullable=\"false\" />";

        private const string ELEMENT_A_GENERATED_PERSISTABLE_FALSE =
            "<assignment source=\"A\" target=\"A\" persistable=\"false\" nullable=\"false\" />";

        private const string ELEMENT_A_GENERATED_NULLABLE_TRUE =
            "<assignment source=\"A\" target=\"A\" persistable=\"true\" nullable=\"true\" />";

        private const string ELEMENT_A_GENERATED_SOURCE_KEY_TRUE =
            "<assignment source=\"A\" target=\"A\" persistable=\"true\" nullable=\"false\" />";

        private const string ELEMENT_A_GENERATED_TARGET_KEY_TRUE =
            "<assignment source=\"A\" target=\"A\" persistable=\"true\" nullable=\"false\" />";

        #endregion

        #region ParseXElement Tests

        [TestMethod]
        public void ParseXElement_WithSourceAndTarget_PopulatesSourceAndTargetProperties()
        {
            // Act
            var actual = new ConcreteAssignment("tableA", "classA");
            actual.ParseXElement(XElement.Parse(ELEMENT_A_MINIMUM));

            // Assert
            Assert.AreEqual(new AssignmentPoint("tableA", "A"), actual.Source);
            Assert.AreEqual(new AssignmentPoint("classA", "A"), actual.Target);
        }

        [TestMethod]
        public void ParseXElement_WithSourceAndTargetOnly_LeavesDefaultValues()
        {
            // Act
            var actual = new ConcreteAssignment("tableA", "classA");
            actual.ParseXElement(XElement.Parse(ELEMENT_A_MINIMUM));

            // Assert
            Assert.IsTrue(actual.Persistable);
            Assert.IsFalse(actual.Nullable);
        }
        
        [TestMethod]
        public void ParseXElement_PersistableIsFalse_SetsPersistablePropertyToFalse()
        {
            // Act
            var actual = new ConcreteAssignment("tableA", "classA");
            actual.ParseXElement(XElement.Parse(ELEMENT_A_PERSISTABLE));

            // Assert
            Assert.IsFalse(actual.Persistable);
        }

        [TestMethod]
        public void ParseXElement_PersistableIsTrue_SetsPersistablePropertyToTrue()
        {   
            // Act
            var actual = new ConcreteAssignment("tableA", "classA");
            actual.ParseXElement(XElement.Parse(ELEMENT_A_PERSISTABLE_TRUE));

            // Assert
            Assert.IsTrue(actual.Persistable);
        }

        [TestMethod]
        public void ParseXElement_NullableIsFalse_SetsNullablePropertyToFalse()
        {
            // Act
            var actual = new ConcreteAssignment("tableA", "classA");
            actual.ParseXElement(XElement.Parse(ELEMENT_A_NULLABLE));

            // Assert
            Assert.IsFalse(actual.Nullable);
        }

        [TestMethod]
        public void ParseXElement_NullableIsTrue_SetsNullablePropertyToTrue()
        {   
            // Act
            var actual = new ConcreteAssignment("tableA", "classA");
            actual.ParseXElement(XElement.Parse(ELEMENT_A_NULLABLE_TRUE));

            // Assert
            Assert.IsTrue(actual.Nullable);
        }

        [TestMethod]
        public void ParseXElement_SourceKeyIsTrue_SetsSourceAssignmentPrimaryKeyToTrue()
        {
            // Act
            var actual = new ConcreteAssignment("tableA", "classA");
            actual.ParseXElement(XElement.Parse(ELEMENT_A_SOURCE_KEY));

            // Assert
            Assert.IsTrue(actual.PrimaryKey);
        }

        [TestMethod]
        public void ParseXElement_SourceKeyIsFalse_SetsSourceAssignmentPrimaryKeyToFalse()
        {
            // Act
            var actual = new ConcreteAssignment("tableA", "classA");
            actual.ParseXElement(XElement.Parse(ELEMENT_A_SOURCE_KEY_FALSE));

            // Assert
            Assert.IsFalse(actual.PrimaryKey);
        }

        [TestMethod]
        public void ParseXElement_TargetKeyIsTrue_PopulatesObject()
        {
            // Act
            var actual = new ConcreteAssignment("tableA", "classA");
            actual.ParseXElement(XElement.Parse(ELEMENT_A_TARGET_KEY));

            // Assert
            Assert.IsTrue(actual.PrimaryKey);
        }

        [TestMethod]
        public void ParseXElement_TargetKeyIsFalse_PopulatesObject()
        {
            // Act
            var actual = new ConcreteAssignment("tableA", "classA");
            actual.ParseXElement(XElement.Parse(ELEMENT_A_TARGET_KEY_FALSE));

            // Assert
            Assert.IsFalse(actual.PrimaryKey);
        }

        #endregion

        #region GetXElementRepresentation Tests

        [TestMethod]
        public void GetXElementRepresentation_UsingDefaultValues_GeneratesXml()
        {
            // Arrange
            var assignment = new ConcreteAssignment("tableA", "classA")
                                 {
                                     Source = new AssignmentPoint("tableA", "A"),
                                     Target = new AssignmentPoint("classA", "A")
                                 };
            var expected = XElement.Parse(ELEMENT_A_GENERATED);

            // Act
            var actual = assignment.GetXElementRepresentation();

            // Assert
            Assert.IsTrue(XNode.DeepEquals(expected, actual));
        }

        [TestMethod]
        public void GetXElementRepresentation_PersistableFalse_GeneratesXml()
        {
            // Arrange
            var assignment = new ConcreteAssignment("tableA", "classA")
                                 {
                                     Source = new AssignmentPoint("tableA", "A"),
                                     Target = new AssignmentPoint("classA", "A"),
                                     Persistable = false
                                 };
            var expected = XElement.Parse(ELEMENT_A_GENERATED_PERSISTABLE_FALSE);

            // Act
            var actual = assignment.GetXElementRepresentation();

            // Assert
            Assert.IsTrue(XNode.DeepEquals(expected, actual));
        }

        [TestMethod]
        public void GetXElementRepresentation_NullableTrue_GeneratesXml()
        {
            // Arrange
            var assignment = new ConcreteAssignment("tableA", "classA")
                                 {
                                     Source = new AssignmentPoint("tableA", "A"),
                                     Target = new AssignmentPoint("classA", "A"),
                                     Nullable = true
                                 };
            var expected = XElement.Parse(ELEMENT_A_GENERATED_NULLABLE_TRUE);

            // Act
            var actual = assignment.GetXElementRepresentation();

            // Assert
            Assert.IsTrue(XNode.DeepEquals(expected, actual));
        }

        [TestMethod]
        public void GetXElementRepresentation_SourceKey_GeneratesXml()
        {
            // Arrange
            var assignment = new ConcreteAssignment("tableA", "classA")
                                 {
                                     Source = new AssignmentPoint("tableA", "A"),
                                     Target = new AssignmentPoint("classA", "A")
                                 };
            var expected = XElement.Parse(ELEMENT_A_GENERATED_SOURCE_KEY_TRUE);

            // Act
            var actual = assignment.GetXElementRepresentation();

            // Assert
            Assert.IsTrue(XNode.DeepEquals(expected, actual));
        }

        [TestMethod]
        public void GetXElementRepresentation_TargetKey_GeneratesXml()
        {
            // Arrange
            var assignment = new ConcreteAssignment("tableA", "classA")
            {
                Source = new AssignmentPoint("tableA", "A"),
                Target = new AssignmentPoint("classA", "A")
            };
            var expected = XElement.Parse(ELEMENT_A_GENERATED_TARGET_KEY_TRUE);

            // Act
            var actual = assignment.GetXElementRepresentation();

            // Assert
            Assert.IsTrue(XNode.DeepEquals(expected, actual));            
        }
        #endregion

        #region Nested Types

        /// <summary>
        /// Concrete implementation of Assignment
        /// </summary>
        private class ConcreteAssignment : Assignment
        {
            public ConcreteAssignment(string sourceEntity, string targetEntity) : base(sourceEntity, targetEntity)
            {
            }
        }

        #endregion
    }
}